package org.swift.commons.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Element;
import org.swift.commons.util.io.ReaderInputStream;

import com.sun.syndication.io.XmlReader;

public class OMUtils {
	@SuppressWarnings("unused")
	private final static Log log = LogFactory.getLog(OMUtils.class);
	
	public static Collection<OMElement> asOMElementList( Collection<Element> elements ) {
		Collection<OMElement> omelements = new ArrayList<OMElement>();
		
		for(Element element : elements) {
			try {
				omelements.add( OMUtils.stringToOMElement(element.asXML()) );
			} catch (XMLStreamException e) {
				throw new RuntimeException(e);
			}
		}
		
		return omelements;
	}
	
	/*
	// Fix these dependencies (try not to include axis2..)
	public static XMLValidationResult validateXml(OMElement xmlDocument,
			Source schemaDocument) throws IOException, SAXException, Exception{
		return XmlUtils.validateXml(org.apache.axis2.util.XMLUtils.toDOM(xmlDocument), schemaDocument);
	}
	
	public static XMLValidationResult validateXml(OMElement xmlDocument,
			Schema schemaDocument) throws IOException, SAXException, Exception{
		return XmlUtils.validateXml(org.apache.axis2.util.XMLUtils.toDOM(xmlDocument), schemaDocument);
	}*/
	
	public static OMElement stringToOMElement( String text ) throws XMLStreamException{
		StAXOMBuilder builder = null;
		try {
			//TODO detect char set
			builder = new StAXOMBuilder( new ByteArrayInputStream(text.getBytes( "UTF-8" )));			
		} catch (UnsupportedEncodingException e) {
			throw new XMLStreamException(e);
		}		
		
		return builder.getDocumentElement();
	}
	
	public static OMElement streamToOMElement( InputStream stream ) throws XMLStreamException{
		StAXOMBuilder builder = new StAXOMBuilder( stream );		
		return builder.getDocumentElement();
	}
	
	/*
	public static void addChildrenToElement( OMElement target, Collection<OMElement> children ){
		for( OMElement child : children ) { target.addChild(child); }
	}
	*/
	public static void addChildrenToElement ( OMElement target,  OMElement... children ){
		for( OMElement child : children ) { target.addChild(child); }
	}
	
	public static OMElement addChildrenToElement ( String root, OMNamespace namespace, OMElement... children ){
		OMFactory factory = OMAbstractFactory.getOMFactory();		
		OMElement rootElement = factory.createOMElement(root, namespace);
		addChildrenToElement(rootElement, children);
		return rootElement;
	}
	
	public static OMElement mapToOMElement(String rootName, Map<String, ?> map) {
		OMFactory factory = OMAbstractFactory.getOMFactory();
		OMElement rootElement = factory.createOMElement(rootName, null);
		map.keySet().iterator();
		for (String name : map.keySet()) {
			OMElement currentElement = factory.createOMElement(name, null, rootElement);
			if (map.get(name) != null) {
				currentElement.setText(map.get(name).toString());
			}
		}	
		return rootElement;
	}
	
	public static OMElement indentXml(OMElement xml) throws TransformerException, XMLStreamException {
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
		//initialize StreamResult with File object to save to file
		StringWriter writer = new StringWriter();
		StreamResult result = new StreamResult(writer);
		
		//OMResult result = new OMResult(); //should work, but for some reason this eliminates the whitespace
		
		DOMSource source;
		try {
			source = new DOMSource(toDOM(xml));
		} catch (Exception e) {
			throw new TransformerException(e);
		}
		transformer.transform(source, result);
		
		return OMUtils.stringToOMElement(writer.toString());
	}
	
	public static org.w3c.dom.Element toDOM(OMElement element) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();        
        element.serialize(baos);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());        
        XmlReader xmlReader = new XmlReader(bais, true);       
        ReaderInputStream readerInputStream = new ReaderInputStream(xmlReader, xmlReader.getEncoding());

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        return factory.newDocumentBuilder().parse(readerInputStream).getDocumentElement();
    }
	
	public static ByteArrayInputStream omToStream(OMElement element) throws XMLStreamException {
		//TODO this could be bad if the result is a large message, probably should dump to disk or 
		// stream the results from the element if possible
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		
		element.serializeAndConsume(baos);
		return new ByteArrayInputStream(baos.toByteArray());				
	}
	
	
}
